home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2007 December / PCWKCD1207B.iso / Blogowanie poza sfera / Flock 1.0 beta / flock-1.0RC3.en-US.win32.exe / flock / components / flockYoutubeService.js < prev    next >
Text File  |  2007-10-18  |  69KB  |  1,990 lines

  1. // vim: ts=2 sw=2 expandtab cindent
  2. //
  3. // BEGIN FLOCK GPL
  4. // 
  5. // Copyright Flock Inc. 2005-2007
  6. // http://flock.com
  7. // 
  8. // This file may be used under the terms of of the
  9. // GNU General Public License Version 2 or later (the "GPL"),
  10. // http://www.gnu.org/licenses/gpl.html
  11. // 
  12. // Software distributed under the License is distributed on an "AS IS" basis,
  13. // WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  14. // for the specific language governing rights and limitations under the
  15. // License.
  16. // 
  17. // END FLOCK GPL
  18.  
  19. // Developer ID = 1aiV9CsTs3o
  20. // Developer Secret = flock_is_open_source
  21.  
  22. const ENABLE_DEBUG = true; // switch to turn off slow debug code for production
  23. function DEBUG(x) { if (ENABLE_DEBUG) debug("flockYouTubeService: "+x+"\n"); }
  24.  
  25. const Cc = Components.classes;
  26. const Ci = Components.interfaces;
  27. const Cr = Components.results;
  28.  
  29. Components.utils.import("resource://gre/modules/JSON.jsm");
  30. Components.utils.import("resource:///modules/FlockScheduler.jsm");
  31.  
  32. const YT_DEVID = "1aiV9CsTs3o";
  33. const YT_DEVSECRET = "flock_is_open_source";
  34. const YOUTUBE_CID = Components.ID('{DCB6A01E-7D4A-4C30-AB3D-9CC98C02F617}');
  35. const YOUTUBE_CONTRACTID = '@flock.com/?photo-api-youtube;1';
  36. const YOUTUBE_FAVICON = "http://www.youtube.com/favicon.ico";
  37. const YOUTUBE_TITLE = "YouTube Web Service";
  38. const SERVICE_ENABLED_PREF          = "flock.service.youtube.enabled";
  39. const CATEGORY_COMPONENT_NAME       = "YouTube JS Component"
  40. const CATEGORY_ENTRY_NAME           = "youtube"
  41. const FRIENDS_AVATAR_TIMEOUT = 5000;  // in milliseconds
  42. const FRIENDS_ACTIVITY_TIMEOUT = 500;  // in milliseconds
  43.  
  44. const FLOCK_PHOTOPERSON_CONTRACTID  = '@flock.com/photo-person;1';
  45.  
  46. const PROPERTY_BAG_CONTRACTID = "@mozilla.org/hash-property-bag;1";
  47. const XHR_CONTRACTID = "@mozilla.org/xmlextras/xmlhttprequest;1";
  48. const FLOCK_RDDS_CONTRACTID = "@flock.com/rich-dnd-service;1";
  49.  
  50. const OBS_TOPIC_XPCOMSHUTDOWN = "xpcom-shutdown";
  51. const YOUTUBE_IDENTITY_URN_PREFIX = "urn:flock:identity:youtube:";
  52. const PEOPLE_PROPERTIES = "chrome://flock/locale/people/people.properties";
  53. const YOUTUBE_STRING_BUNDLE = "chrome://flock/locale/services/youtube.properties";
  54.  
  55. // From nsIXMLHttpRequest.idl
  56. // 4: COMPLETED     Finished with all operations.
  57. const XMLHTTPREQUEST_READYSTATE_COMPLETED = 4;
  58.  
  59. const HTTP_CODE_OK = 200;
  60.  
  61. // The delay between two refreshes when the sidebar is closed (in seconds)
  62. const YOUTUBE_REFRESH_INTERVAL = 3600; // 60 minutes
  63. // The delay between two refreshes when the sidebar is open (in seconds)
  64. const YOUTUBE_SHORT_INTERVAL = 3600; // 60 minutes
  65.  
  66.  
  67. var gCompTK;
  68. function getCompTK() {
  69.   if (!gCompTK) {
  70.     gCompTK = Cc["@flock.com/singleton;1"]
  71.                 .getService(Ci.flockISingleton)
  72.                 .getSingleton("chrome://browser/content/flock/services/common/load-compTK.js")
  73.                 .wrappedJSObject;
  74.   }
  75.   return gCompTK;
  76. }
  77.  
  78. function _getIdentityUrn(aAccountId, aUid) {
  79.   var result = YOUTUBE_IDENTITY_URN_PREFIX
  80.              + aAccountId + ":"
  81.              + aUid;
  82.   return result;
  83. }
  84.  
  85. loadLibraryFromSpec("chrome://browser/content/flock/photo/photoAPI.js");
  86.  
  87. var gTimers = [];  // For use with the scheduler
  88.  
  89. // String defaults... may be updated later through Web Detective
  90. var gStrings = {
  91.   "domains": "youtube.com",
  92.   "userlogin": "http://www.youtube.com/login",
  93.   "userprofile": "http://www.youtube.com/profile?user=%accountid%",
  94.   "editprofile": "http://youtube.com/my_profile",
  95.   "inbox": "http://youtube.com/my_messages",
  96.   "noavatarimage": "no_videos_140.jpg"
  97. };
  98.  
  99. function youtubeVideo() {
  100. }
  101.  
  102. youtubeVideo.prototype= {
  103.   id: "",
  104.   thumbnail: "",
  105.   webPageUrl: "",
  106.   midSizePhoto: "",
  107.   largeSizePhoto: "",
  108.   title: "",
  109.   username: "",
  110.   userid: "",
  111.   is_public: "true",
  112.   is_video: "true",
  113.   has_miniView: "true",
  114.   svcShortName: 'youtube',
  115.   buildTooltip: function( ) {
  116.     // do we have to use document from the window to ceate elements? -- ja
  117.     var wm = Cc["@mozilla.org/appshell/window-mediator;1"]
  118.                 .getService(Ci.nsIWindowMediator);
  119.     var win = wm.getMostRecentWindow('navigator:browser');
  120.     if (!win) return null;
  121.  
  122.     var box = win.document.createElement('vbox');
  123.     box.setAttribute('style', 'max-width: 250px');
  124.  
  125.     var title = win.document.createElement('label');
  126.     title.setAttribute('value', this.title );
  127.     title.setAttribute('crop', 'end');
  128.     box.appendChild(title);
  129.  
  130.     if( this.length_seconds)
  131.     {
  132.       var lbl = win.document.createElement('label');
  133.       lbl.setAttribute('value', 'Length: ' + this.length_seconds + ' seconds');
  134.       box.appendChild(lbl);
  135.     }
  136.     if(this.rating_avg)
  137.     {
  138.       var lbl = win.document.createElement('label');
  139.       lbl.setAttribute('value', 'Rating: ' + this.rating_avg + ' (' + this.rating_count + ' times)');
  140.       box.appendChild(lbl);
  141.     }
  142.     if(this.view_count)
  143.     {
  144.       var lbl = win.document.createElement('label');
  145.       lbl.setAttribute('value', 'Views: ' + this.view_count );
  146.       box.appendChild(lbl);
  147.     }
  148.  
  149.     if(!(this.length_seconds && this.rating_avg && this.view_count))
  150.     {
  151.       //if the tooltip does not these metadata -- show the author
  152.       var lbl = win.document.createElement('label');
  153.       lbl.setAttribute('value', this.username );
  154.       lbl.setAttribute('class', 'user');
  155.       box.appendChild(lbl);
  156.     }
  157.  
  158.     var vbox = win.document.createElement('vbox');
  159.     var cbox = win.document.createElement('cbox');
  160.     var largeImg = win.document.createElement('image');
  161.     largeImg.setAttribute('src', this.midSizePhoto);
  162.     largeImg.setAttribute('style', 'margin-bottom: 2px;');
  163.     var spacer = win.document.createElement('spacer');
  164.     spacer.setAttribute('flex', '1');
  165.     cbox.appendChild(largeImg);
  166.     cbox.appendChild(spacer);
  167.     vbox.appendChild(cbox);
  168.     vbox.appendChild(box);
  169.  
  170.     return vbox;
  171.   },
  172.   buildHTML: function ( ) {
  173.     var flashUrl = this.webPageUrl.replace(/\/\?v\=/,'/v/');
  174.  
  175.     return '<object width="425" height="350">'
  176.          + '<param name="movie" value="'+flashUrl+'"/>'
  177.          + '<embed src="'+flashUrl+'" type="application/x-shockwave-flash" width="425" height="350"/>'
  178.          + this.webPageUrl
  179.          + '</object>';
  180.   },
  181.   buildBBCode: function ( ) {
  182.     var video = this.webPageUrl.replace(/\/\?v\=/,'/watch?v=');
  183.     return '[youtube]' + video + '[/youtube]'
  184.   },
  185.   buildMiniPage: function ( ) {
  186.     var theurl = this.webPageUrl.replace(/\/\?v\=/,'/v/');
  187.     return '<html><head><title>' + this.title + ' (' + this.username + ')</title></head>' +
  188.            '<body><object width="425" height="350">' +
  189.            '<param name="movie" value="'+theurl+'"/>' +
  190.            '<center><embed src="'+theurl+'&autoplay=1" type="application/x-shockwave-flash" width="425" height="350"/></object></center></body></html>';
  191.   },
  192.  
  193.   QueryInterface: function(iid) {
  194.     if (!iid.equals(Ci.nsISupports) &&
  195.         !iid.equals(Ci.flockIPhoto)) {
  196.       throw Components.results.NS_ERROR_NO_INTERFACE;
  197.     }
  198.     return this;
  199.   }
  200. };
  201.  
  202. youtubeVideo.prototype.__defineGetter__('metaData', function () {
  203.   var metaData = Cc["@mozilla.org/hash-property-bag;1"].createInstance(Ci.nsIWritablePropertyBag2);
  204.   metaData.setPropertyAsAString("title", this.title);
  205.   metaData.setPropertyAsAString("length", this.length_seconds);
  206.   metaData.setPropertyAsAString("views", this.view_count);
  207.   metaData.setPropertyAsAString("rating", this.rating_avg);
  208.  
  209.   metaData.QueryInterface(Ci.nsIPropertyBag);
  210.  
  211.   return metaData;
  212. })
  213.  
  214.  
  215. // ================================================
  216. // ========== BEGIN youtubeService class ==========
  217. // ================================================
  218.  
  219. function youtubeService()
  220. {
  221.   this.obs = Components.classes["@mozilla.org/observer-service;1"]
  222.                        .getService(Components.interfaces.nsIObserverService);
  223.   this.obs.addObserver(this, OBS_TOPIC_XPCOMSHUTDOWN, false);
  224.   this.acUtils = Components.classes["@flock.com/account-utils;1"]
  225.                            .getService(Components.interfaces.flockIAccountUtils);
  226.   this.status = Components.interfaces.flockIWebService.STATUS_UNKNOWN;
  227.   this.url = "http://www.youtube.com";
  228.   this.mIsInitialized = false;
  229.   this._ctk = {
  230.     interfaces: [
  231.       "nsISupports",
  232.       "nsISupportsCString",
  233.       "nsIClassInfo",
  234.       "nsIObserver",
  235.       "flockIWebService",
  236.       "flockIMediaWebService",
  237.       "flockISocialWebService",
  238.       "flockIYoutubeService",
  239.       "flockIManageableWebService",
  240.       "flockIPollingService",
  241.       "flockIRichContentDropHandler"
  242.     ],
  243.     shortName: "youtube",
  244.     fullName: "YouTube",
  245.     description: YOUTUBE_TITLE,
  246.     favicon: YOUTUBE_FAVICON,
  247.     CID: YOUTUBE_CID,
  248.     contractID: YOUTUBE_CONTRACTID,
  249.     accountClass: youtubeAccount,
  250.     needPassword: false
  251.   };
  252.  
  253.   var sbs = Cc["@mozilla.org/intl/stringbundle;1"]
  254.             .getService(Ci.nsIStringBundleService);
  255.   var bundle = sbs.createBundle(YOUTUBE_STRING_BUNDLE);
  256.  
  257.   this._channels = {
  258.     "special:added": {
  259.       title: bundle.GetStringFromName("flock.youtube.title.added"),
  260.       supportsSearch: false,
  261.       feed: "http://youtube.com/rss/global/recently_added.rss"
  262.     },
  263.     "special:featured": {
  264.       title: bundle.GetStringFromName("flock.youtube.title.featured"),
  265.       supportsSearch: false
  266.     },
  267.     "special:top_favorites": {
  268.       title: bundle.GetStringFromName("flock.youtube.title.topfav"),
  269.       supportsSearch: false,
  270.       feed: "http://youtube.com/rss/global/top_favorites.rss"
  271.     },
  272.     "special:rated": {
  273.       title: bundle.GetStringFromName("flock.youtube.title.rated"),
  274.       supportsSearch: false,
  275.       feed: "http://youtube.com/rss/global/top_rated.rss"
  276.     }
  277.   };
  278.  
  279.   this._logger = Cc['@flock.com/logger;1'].createInstance(Ci.flockILogger);
  280.   this._logger.init('youtube');
  281.   this._profiler = Cc["@flock.com/profiler;1"].getService(Ci.flockIProfiler);
  282.   this.init();
  283. }
  284.  
  285.  
  286. // BEGIN nsIObserver interface
  287. youtubeService.prototype.observe =
  288. function youtubeService_observe(subject, topic, state)
  289. {
  290.   switch (topic) {
  291.     case OBS_TOPIC_XPCOMSHUTDOWN:
  292.     {
  293.       this.obs.removeObserver(this, OBS_TOPIC_XPCOMSHUTDOWN);
  294.     }; break;
  295.   }
  296. }
  297. // END nsIObserver interface
  298.  
  299.  
  300. youtubeService.prototype.init =
  301. function youtubeService_init()
  302. {
  303.   DEBUG(".init()");
  304.  
  305.   // Prevent re-entry
  306.   if (this.mIsInitialized) return;
  307.   this.mIsInitialized = true;
  308.  
  309.   var evtID = this._profiler.profileEventStart("youtube-init");
  310.  
  311.   this.prefService = Components.classes["@mozilla.org/preferences-service;1"]
  312.                                .getService(Components.interfaces.nsIPrefBranch);
  313.   if ( this.prefService.getPrefType(SERVICE_ENABLED_PREF) &&
  314.        !this.prefService.getBoolPref(SERVICE_ENABLED_PREF) )
  315.   {
  316.     DEBUG("Pref "+SERVICE_ENABLED_PREF+" set to FALSE... not initializing.");
  317.     var catMgr = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
  318.     catMgr.deleteCategoryEntry("wsm-startup", CATEGORY_COMPONENT_NAME, true);
  319.     catMgr.deleteCategoryEntry("flockWebService", CATEGORY_ENTRY_NAME, true);
  320.     catMgr.deleteCategoryEntry("flockMediaProvider", CATEGORY_ENTRY_NAME, true);
  321.     return;
  322.   }
  323.  
  324.   this.faves_coop = Components.classes['@flock.com/singleton;1']
  325.                               .getService(Components.interfaces.flockISingleton)
  326.                               .getSingleton("chrome://flock/content/common/load-faves-coop.js")
  327.                               .wrappedJSObject;
  328.   this.account_root = this.faves_coop.accounts_root;
  329.  
  330.   this.ytService = new this.faves_coop.Service(
  331.     "urn:youtube:service",
  332.     {
  333.       name: "youtube",
  334.       desc: "The YouTube Service",
  335.       loginURL: gStrings["userlogin"],
  336.       contactLabel: 'Contacts'
  337.     }
  338.   );
  339.   this.ytService.serviceId = YOUTUBE_CONTRACTID;
  340.  
  341.   // Load Web Detective file
  342.   this.webDetective = this.acUtils.useWebDetective("youtube.xml");
  343.   for (var s in gStrings) {
  344.     gStrings[s] = this.webDetective.getString("youtube", s, gStrings[s]);
  345.   }
  346.   this.ytService.domains = gStrings["domains"];
  347.  
  348.   this.urn = this.ytService.id();
  349.  
  350.   this._updateFriendAvatarsComplete = false;
  351.   
  352.   this._updateFriendActivityComplete = false;
  353.   this._friendActivityArray = [];
  354.   
  355.   this._profiler.profileEventEnd(evtID, "");
  356. }
  357.  
  358. youtubeService.prototype.getError =
  359. function youtubeService_getError (aErrorType, aXML, aHTTPErrorCode) {
  360.   var error = Components.classes["@flock.com/error;1"].createInstance(Ci.flockIError);
  361.   if  (aErrorType == "HTTP_ERROR") {
  362.     error.errorCode = aHTTPErrorCode;
  363.   } else if (aErrorType == "SERVICE_ERROR") {
  364.     var errorCode;
  365.     var errorMessage;
  366.     var serviceErrorMessage;
  367.     try {
  368.       errorCode = aXML.getElementsByTagName("error")[0].getAttribute('code');
  369.       serviceErrorMessage = aXML.getElementsByTagName("error")[0].getAttribute('description');
  370.     } catch (ex) {
  371.       errorCode = "999" // in case the error xml is invalid
  372.     }
  373.  
  374.     switch (errorCode) { // http://www.youtube.com/dev_error_codes
  375.       case "1":
  376.         error.errorCode = error.HTTP_INTERNAL_SERVER_ERROR;
  377.       break;
  378.  
  379.       case "2": // These errors are due to Flock sending a bad request
  380.       case "3":
  381.       case "4":
  382.       case "5":
  383.       case "6":
  384.         error.errorCode = error.PHOTOSERVICE_INVALID_QUERY;
  385.       break;
  386.  
  387.       case "7":
  388.       case "8":
  389.         error.errorCode = error.PHOTOSERVICE_INVALID_API_KEY;
  390.         break;
  391.  
  392.       case "999":
  393.         error.errorCode = error.PHOTOSERVICE_UNKNOWN_ERROR;
  394.       break;
  395.  
  396.       default:
  397.         error.errorCode = error.PHOTOSERVICE_UNKNOWN_ERROR;
  398.       break;
  399.     }
  400.   }
  401.   error.serviceErrorCode = errorCode;
  402.   error.serviceErrorString = serviceErrorMessage;
  403.   this._logger.error(error.errorString);
  404.   return error;
  405. };
  406.  
  407. youtubeService.prototype.supportsSearch =
  408. function youtubeService_supportsSearch( aQueryString ) {
  409.   var aQuery = new queryHelper(aQueryString);
  410.  
  411.   if (aQuery.special) {
  412.     var channel = this._channels["special:" + aQuery.special];
  413.     if (channel) {
  414.       return channel.supportsSearch;
  415.     }
  416.   }
  417.  
  418.   // none of the other apis/feeds support search
  419.   return false;
  420. }
  421.  
  422. youtubeService.prototype.call =
  423. function youtubeService_call(aListener, aMethod, aParams)
  424. {
  425.   var url = "http://www.youtube.com/api2_rest?method=" + aMethod + "&dev_id=" + YT_DEVID;
  426.   for (p in aParams) {
  427.     url += "&" + p + "=" + aParams[p];
  428.   }
  429.   var hr = Components.classes['@mozilla.org/xmlextras/xmlhttprequest;1']
  430.                      .createInstance(Components.interfaces.nsIXMLHttpRequest)
  431.                      .QueryInterface(Components.interfaces.nsIJSXMLHttpRequest);
  432.  
  433.   var inst = this;
  434.   hr.onreadystatechange = function (aEvt) {
  435.     if (hr.readyState == 4) {
  436.       try {
  437.         if (hr.status/100 == 2) {
  438.           var rsp = hr.responseXML.getElementsByTagName("ut_response")[0];
  439.           var stat = rsp.getAttribute("status");
  440.           if (stat != "ok") {
  441.             var error = inst.getError('SERVICE_ERROR', hr.responseXML, null);
  442.             aListener.onError(error);
  443.           }
  444.           else {
  445.             aListener.onResult(hr.responseXML);
  446.           }
  447.         }
  448.         else {
  449.           // http errors
  450.           aListener.onError(inst.getError("HTTP_ERROR", null, hr.status));
  451.         }
  452.       } catch(e) {
  453.         // XMHTTPERROR (connection lost)
  454.         inst._logger.error(e);
  455.         aListener.onError(inst.getError("HTTP_ERROR", null, "9001"));
  456.       }
  457.     }
  458.   };
  459.  
  460.   hr.backgroundRequest = true;
  461.   hr.open('GET',  url,true);
  462.   hr.send(null);
  463. }
  464.  
  465. youtubeService.prototype.getFeed =
  466. function youtubeService_getFeed(aListener, aFeedURL)
  467. {
  468.   ios = Components.classes["@mozilla.org/network/io-service;1"]
  469.     .getService(Components.interfaces.nsIIOService);
  470.   var uri = ios.newURI(aFeedURL, null, null);
  471.  
  472.   var ytServ = this;
  473.   var feedListener = {
  474.     onGetFeedComplete: function oncomplete (feed) {
  475.       ytServ.handleFeedResult(aListener, feed);
  476.     },
  477.     onError: aListener.onError
  478.   };
  479.  
  480.   var fm = Components.classes["@flock.com/feed-manager;1"]
  481.     .getService(Components.interfaces.flockIFeedManager);
  482.   fm.getFeedBypassCache(uri, feedListener);
  483. }
  484.  
  485. youtubeService.prototype.createAlbum =
  486. function youtubeService_createAlbum(aListener, aAlbumName)
  487. {
  488.   throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  489. }
  490.  
  491. youtubeService.prototype.findByUsername =
  492. function youtubeService_findByUsername(aListener, aUsername)
  493. {
  494.   var inst = this;
  495.   var myListener = {
  496.     onResult: function (aXML) {
  497.       var newUserObj = Components.classes[FLOCK_PHOTOPERSON_CONTRACTID]
  498.                                  .createInstance(Components.interfaces.flockIPhotoPerson);
  499.       newUserObj.service = inst;
  500.       newUserObj.id = aUsername;
  501.       newUserObj.username = aUsername;
  502.       newUserObj.fullname = aUsername;
  503.       aListener.onFindByUsernameResult(newUserObj);
  504.     },
  505.     onError: function (aXML) {
  506.       aListener.onError(aXML);
  507.     }
  508.   }
  509.   var params = {};
  510.   params.user = aUsername;
  511.   this.call(myListener, "youtube.users.get_profile", params);
  512. }
  513.  
  514. youtubeService.prototype.getAlbums = function(aListener, aUsername) { throw Components.results.NS_ERROR_NOT_IMPLEMENTED; }
  515. youtubeService.prototype.getAuthPerson = function() {
  516.   if (!this.user) return null;
  517.   var newUserObj = Components.classes[FLOCK_PHOTOPERSON_CONTRACTID]
  518.                              .createInstance(Components.interfaces.flockIPhotoPerson);
  519.   newUserObj.id = this.api.user.username
  520.   newUserObj.username = this.api.user.username;
  521.   newUserObj.fullname = this.api.user.username;
  522.   newUserObj.service = this;
  523.   return newUserObj;
  524. }
  525. youtubeService.prototype.getContacts = function(aListener) { throw Components.results.NS_ERROR_NOT_IMPLEMENTED; }
  526. youtubeService.prototype.getMostRecentPhotoForList = function(aListener, aEnumerator) { throw Components.results.NS_ERROR_NOT_IMPLEMENTED; }
  527. youtubeService.prototype.getPhoto = function(aListener, aPhotoID) { throw Components.results.NS_ERROR_NOT_IMPLEMENTED; }
  528. youtubeService.prototype.login = function(aAccountURN, aListener) { throw Components.results.NS_ERROR_NOT_IMPLEMENTED; }
  529.  
  530. youtubeService.prototype.queryChannel =
  531. function youtubeService_queryChannel(aListener, aQueryString, aCount, aPage)
  532. {
  533.   var aQuery = new queryHelper(aQueryString);
  534.   // return; // XXX TODO FIXME: This is killing perf
  535.  
  536.   var inst = this;
  537.   var myListener = {
  538.     onResult: function (aXML) {
  539.       var rval = inst.handlePhotosResult(aXML);
  540.       var enum_ = {
  541.         hasMoreElements: function() {
  542.           return (rval.length > 0);
  543.         },
  544.         getNext: function() {
  545.           return rval.shift();
  546.         }
  547.       }
  548.       aListener.onSearchResult(enum_);
  549.     },
  550.     onError: function (aError) {
  551.       aListener.onError(aError);
  552.     }
  553.   }
  554.  
  555.   var params = {}
  556.   if (aQuery.search) {
  557.     params.tag = aQuery.search;
  558.     params.page = aPage;
  559.     params.per_page = aCount;
  560.     this.call(myListener, 'youtube.videos.list_by_tag', params);
  561.   } else {
  562.     // this API call doesn't support pagination and only shows most recent 25 items
  563.     if (aPage > 1) return;
  564.  
  565.     var channel = this._channels[aQuery.stringVal];
  566.     if (!channel) return;
  567.  
  568.     if (channel.feed)
  569.       this.getFeed(aListener, channel.feed);
  570.     else
  571.       this.call(myListener, "youtube.videos.list_featured", params);
  572.   }
  573. }
  574.  
  575. youtubeService.prototype.search =
  576. function youtubeService_search( aListener, aQueryString, aCount, aPage )
  577. {
  578.   var aQuery = new queryHelper(aQueryString);
  579.   if (aPage > 1) return; // youtube doesn't support pagination
  580.   if(aQuery.favorites && !aQuery.user)
  581.   {
  582.     aQuery.user = aQuery.favorites;
  583.   }
  584.   if (!aQuery.user && aQuery.special != "favorites") {
  585.        this.queryChannel( aListener, aQueryString, aCount, aPage );
  586.        return;
  587.   }
  588.  
  589.   var aUserid = aQuery.user;
  590.  
  591.   var params = {
  592.     user: aUserid
  593.   };
  594.  
  595.   var inst = this;
  596.   var myListener = {
  597.     onResult: function (aXML) {
  598.       var rval = inst.handlePhotosResult(aXML, aUserid);
  599.       var enum_ = {
  600.         hasMoreElements: function() {
  601.           return (rval.length > 0);
  602.         },
  603.         getNext: function() {
  604.           return rval.shift();
  605.         }
  606.       }
  607.       aListener.onSearchResult(enum_);
  608.     },
  609.     onError: function (aError) {
  610.       aListener.onError(aError);
  611.     }
  612.   }
  613.  
  614.   if (aQuery.favorites) {
  615.     this.call(myListener, "youtube.users.list_favorite_videos", params);
  616.   } else {
  617.     this.call(myListener, "youtube.videos.list_by_user", params);
  618.   }
  619. }
  620.  
  621. youtubeService.prototype.getPhotoFromRDFNode =
  622. function (aRDFId)
  623. {
  624.   var newPhoto = new youtubeVideo();
  625.   var coopPhoto = this.faves_coop.get(aRDFId);
  626.   newPhoto.webPageUrl = coopPhoto.URL;
  627.   newPhoto.thumbnail = coopPhoto.thumbnail;
  628.   newPhoto.midSizePhoto = coopPhoto.midSizePhoto;
  629.   newPhoto.largeSizePhoto = coopPhoto.largeSizePhoto;
  630.   newPhoto.username = coopPhoto.username;
  631.   newPhoto.userid = coopPhoto.userid;
  632.   newPhoto.title = coopPhoto.name;
  633.   newPhoto.id = coopPhoto.photoid;
  634.   newPhoto.icon = coopPhoto.favicon;
  635.   newPhoto.uploadDate = coopPhoto.datevalue;
  636.   newPhoto.is_public = coopPhoto.is_public;
  637.   newPhoto.is_video = "true";
  638.   return newPhoto;
  639.   // JMC XXX TODO: Gotta add the video-specific fields into the RDF, or somewhere
  640.   // eg content length, rating, etc.
  641.  
  642. }
  643.  
  644. youtubeService.prototype.handlePhotosResult =
  645. function youtubeService_handlePhotoResult(aXML, aUserid)
  646. {
  647.   var rval = [];
  648.   var photoList = aXML.getElementsByTagName("video");
  649.   for (var i = 0; i < photoList.length; i++) {
  650.     var photo = photoList[i];
  651.     var newPhoto = new youtubeVideo();
  652.     for (var j = 0; j < photo.childNodes.length; j++)
  653.     {
  654.       var child = photo.childNodes[j];
  655.       var contentNode = child.childNodes[0];
  656.       var childContent = "";
  657.       if (contentNode) childContent = contentNode.nodeValue;
  658.       switch (child.tagName)
  659.       {
  660.         case "author":
  661.           newPhoto.username   = childContent;
  662.           newPhoto.userid   = childContent;
  663.         break;
  664.         // case "id":     newPhoto.id     = childContent; break;
  665.         case "title":   newPhoto.title     = childContent; break;
  666.         case "upload_time":
  667.           newPhoto.uploadDate = childContent * 1000;
  668.           newPhoto.id = parseInt(childContent);
  669.         break;
  670.         case "length_seconds":
  671.           newPhoto.length_seconds = childContent;
  672.           break
  673.         case "rating_avg":
  674.           newPhoto.rating_avg = childContent;
  675.           break
  676.         case "description":
  677.           newPhoto.description = childContent;
  678.           break
  679.         case "comment_count":
  680.           newPhoto.comment_count = childContent;
  681.           break
  682.         case "view_count":
  683.           newPhoto.view_count = childContent;
  684.           break
  685.         case "rating_count":
  686.           newPhoto.rating_count = childContent;
  687.           break
  688.         case "url":
  689.           newPhoto.webPageUrl  = childContent;
  690.         break;
  691.         case "thumbnail_url":
  692.           newPhoto.thumbnail     = childContent;
  693.           newPhoto.midSizePhoto   = childContent;
  694.           newPhoto.largeSizePhoto = childContent;
  695.         break;
  696.         // case "id":     newPhoto.id = childContent;
  697.         // break;
  698.       }
  699.     }
  700.  
  701.     newPhoto.is_public = "true";
  702.     newPhoto.is_video = true;
  703.     rval.push(newPhoto);
  704.   }
  705.   return rval;
  706. }
  707.  
  708. youtubeService.prototype.handleFeedResult =
  709. function youtubeService_handleFeedResult(aListener, aFeed)
  710. {
  711.   var photos = [];
  712.  
  713.   var items = aFeed.getItems();
  714.   while (items && items.hasMoreElements()) {
  715.     var item = items.getNext();
  716.  
  717.     var newPhoto = new youtubeVideo();
  718.  
  719.     newPhoto.title = item.getTitle();
  720.     newPhoto.webPageUrl = item.getLink().spec;
  721.  
  722.     newPhoto.uploadDate = item.getPubDate();
  723.     newPhoto.id = newPhoto.uploadDate / 1000;
  724.  
  725.     var author = item.getAuthor();
  726.     newPhoto.username = author;
  727.     newPhoto.userid = author;
  728.  
  729.     var desc = item.getContent();
  730.     var re = /<img [^>]*src="([^"]*)"/i;
  731.     var thumbnail = desc.match(re)[1];
  732.     newPhoto.thumbnail = thumbnail;
  733.     newPhoto.midSizePhoto = thumbnail;
  734.     newPhoto.largeSizePhoto = thumbnail;
  735.  
  736.     newPhoto.is_public = "true";
  737.     newPhoto.is_video = true;
  738.  
  739.     photos.push(newPhoto);
  740.   }
  741.  
  742.   var enum_ = {
  743.     hasMoreElements: function() {
  744.       return (photos.length > 0);
  745.     },
  746.     getNext: function() {
  747.       return photos.shift();
  748.     }
  749.   }
  750.   aListener.onSearchResult(enum_);
  751. }
  752.  
  753.  
  754. youtubeService.prototype.supportsFeature = function(aFeature) {
  755.   var supports = {};
  756.   supports.tags = true;
  757.   supports.title = true;
  758.   supports.fileName = false;
  759.   supports.contacts = true;
  760.   supports.privacy = false;
  761.   supports.albumCreation = false;
  762.   return (supports[aFeature] == true);
  763. }
  764. youtubeService.prototype.upload = function(aListener, aFile, aParams, aUpload) {  throw Components.results.NS_ERROR_NOT_IMPLEMENTED; }
  765.  
  766.  
  767. youtubeService.prototype.refresh =
  768. function youtubeService_refresh(aURN, aListener)
  769. {
  770.   DEBUG("refresh with aURN of " + aURN);
  771.   var refreshItem = this.faves_coop.get(aURN);
  772.  
  773.   if (refreshItem.isInstanceOf(this.faves_coop.Account)) {
  774.     this._logger.debug("refreshing an Account");
  775.     if (refreshItem.isAuthenticated) {
  776.       this.refreshAccount(aURN, aListener);
  777.     } else {
  778.       // If the user is not logged in, return a success without
  779.       // refreshing anything
  780.       aListener.onResult();
  781.     }
  782.   } else {
  783.     throw Components.results.NS_ERROR_ABORT;
  784.   }
  785. }
  786.  
  787. youtubeService.prototype.refreshAccount =
  788. function youtubeService_refreshAccount(aURN, aListener)
  789. {
  790.   if (!aURN) return;
  791.   var inst = this;
  792.   var coopAcct = this.faves_coop.get(aURN);
  793.   var individualSuccess = {};
  794.   individualSuccess.numberOfAnswers  = 0;
  795.   individualSuccess.listener = aListener;
  796.   individualSuccess.coopAcct = coopAcct;
  797.   individualSuccess.refreshNumber = 3;
  798.   
  799.   // get friends
  800.   var getFriendsListener = {
  801.     onResult: function getFriendsListener_onResult(aXML) {
  802.       inst._handleFriendsResult(aXML, coopAcct, individualSuccess);
  803.     },
  804.     onError: function getFriendsListener_onError(aXML) {
  805.       inst._logger.error("Error on getFriendsListener");
  806.     }
  807.   }
  808.  
  809.   // get new message count
  810.   var hr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
  811.            .createInstance(Ci.nsIXMLHttpRequest);
  812.   hr.onreadystatechange = function updateUserAccount_onreadystatechange(aEvent) {
  813.     if (hr.readyState == 4) {
  814.       var results = Cc["@mozilla.org/hash-property-bag;1"]
  815.                     .createInstance(Ci.nsIWritablePropertyBag2);
  816.       if (inst.webDetective.detectNoDOM("youtube", "accountinfo", "",
  817.                                         hr.responseText, results))
  818.       {
  819.         // Set the message count
  820.         var messages = 0;
  821.         var msgCount = results.getPropertyAsAString("messages");
  822.         if (msgCount && msgCount.length) {
  823.           messages = msgCount;
  824.         }
  825.         inst._logger.debug("coopAcct.accountMessages " + messages);
  826.         if (coopAcct.accountMessages < messages) {
  827.           inst._lightPeopleIcon();
  828.         }
  829.         coopAcct.accountMessages = messages;
  830.  
  831.         // Set the avatar for the mecard.
  832.         // If avatar returned as YT no avatar, then set coop avatar to
  833.         // null and let people sidebar code set the Flock no avatar img.
  834.         if (results.getPropertyAsAString("avatar")
  835.                    .match(gStrings["noavatarimage"])) {
  836.           coopAcct.avatar = null;
  837.           inst._logger.debug("no avatar for account, setting to null\n");
  838.         } else {
  839.           var avatar = results.getPropertyAsAString("avatar");
  840.           coopAcct.avatar = inst._fixRelativeAvatarUrl(avatar);
  841.           inst._logger.debug("coopAcct.accountAvatar " + coopAcct.avatar);
  842.         }
  843.       } else {
  844.         coopAcct.accountMessages = 0;
  845.       }
  846.       inst._onIndividualSuccess(individualSuccess);
  847.     }
  848.   }
  849.   hr.open("GET", gStrings["userprofile"].replace("%accountid%", coopAcct.id()));
  850.   hr.send(null);
  851.  
  852.   var params = {};
  853.   params.user = coopAcct.accountId;
  854.   this.call(getFriendsListener, "youtube.users.list_friends", params);
  855. }
  856.  
  857. youtubeService.prototype._onIndividualSuccess =
  858. function youtubeService__onIndividualSuccess(aObj) {
  859.   aObj.numberOfAnswers++;
  860.   if (aObj.numberOfAnswers >= aObj.refreshNumber) {
  861.     if (this.acUtils.isPeopleSidebarOpen()) {
  862.       aObj.coopAcct.nextRefresh = new Date(Date.now()
  863.                                            + YOUTUBE_SHORT_INTERVAL * 1000);
  864.     }
  865.     if (aObj.listener) {
  866.       aObj.listener.onResult();
  867.     }
  868.   }
  869. }
  870.  
  871. youtubeService.prototype.markAllMediaSeen =
  872. function youtubeService_markAllMediaSeen(aIdentityUrn) {
  873.   var identity = this.faves_coop.get(aIdentityUrn);
  874.   identity.unseenMedia = 0;
  875.  
  876. youtubeService.prototype._lightPeopleIcon =
  877. function youtubeService__lightPeopleIcon() {
  878.   this._logger.debug("._lightPeopleIcon()");
  879.   this.obs.notifyObservers(null, "new-people-notification", null);
  880. }
  881.  
  882. youtubeService.prototype._updateFriendAvatars =
  883. function youtubeService__updateFriendAvatars(aURN, aInvidualSuccessObj) {
  884.   this._logger.info("._updateFriendAvatars");
  885.  
  886.   if (this._updateFriendAvatarsComplete || !aURN) {
  887.     return;
  888.   }
  889.  
  890.   // Array of friends accounts (coop objects, not URNs) used to get avatars
  891.   var friendsList = [];
  892.  
  893.   // Retrieve the account's friends list and add friends currently without an
  894.   // avatar to a list to be updated at a later time
  895.   var friendsEnum = this.faves_coop.get(aURN).friendsList.children.enumerate();
  896.   while (friendsEnum.hasMoreElements()) {
  897.     var friend = friendsEnum.getNext();
  898.     if (friend.avatar == "") {
  899.       this._logger.debug("adding friend with missing avatar for updating: "
  900.                          + friend.accountId + "\n");
  901.       friendsList.push(friend);
  902.       // Increment number count that onIndividualSucess needs to count to
  903.       aInvidualSuccessObj.refreshNumber++;
  904.     }
  905.   }
  906.  
  907.   var inst = this;
  908.  
  909.   var friendsTimerCallback = {
  910.     notify: function friendNotify(aTimer) {
  911.       // Scrape the friend page for their avatar
  912.       if (friendsList.length > 0) {
  913.         var friend = friendsList.shift();
  914.         var page = gStrings["userprofile"]
  915.                    .replace("%accountid%", friend.accountId);
  916.         var xhr = Cc[XHR_CONTRACTID].createInstance(Ci.nsIXMLHttpRequest);
  917.         xhr.open("GET", page, true);
  918.  
  919.         xhr.onreadystatechange = function friend_OnReadyStateChange(aEvent) {
  920.           if (xhr.readyState == XMLHTTPREQUEST_READYSTATE_COMPLETED) {
  921.             if (xhr.status == HTTP_CODE_OK) {
  922.               var results = Cc[PROPERTY_BAG_CONTRACTID]
  923.                             .createInstance(Ci.nsIWritablePropertyBag2);
  924.               if (inst.webDetective.detectNoDOM("youtube", "page", "",
  925.                                                 xhr.responseText, results))
  926.               {
  927.                 // If avatar returned as YT no avatar, then set coop avatar to
  928.                 // null and let people sidebar code set the Flock no avatar img.
  929.                 if (results.getPropertyAsAString("avatar")
  930.                            .match(gStrings["noavatarimage"])) {
  931.                   friend.avatar = null;
  932.                   inst._logger.debug("no avatar for friend: " + friend.accountId + "\n");
  933.                 } else {
  934.                   friend.avatar = results.getPropertyAsAString("avatar");
  935.                   friend.avatar = inst._fixRelativeAvatarUrl(friend.avatar);
  936.                   inst._logger.debug("found friend's avatar: " + friend.accountId
  937.                                      + ", " + friend.avatar + "\n");
  938.                 }
  939.               }
  940.               inst._onIndividualSuccess(aInvidualSuccessObj);
  941.             }
  942.           }
  943.         }
  944.  
  945.         inst._logger.debug("requesting avatar for: " + page + "\n");
  946.         xhr.send(null);
  947.       }
  948.  
  949.       // Kill the timer if there's nothing left to update in the array
  950.       if (friendsList.length < 1) {
  951.         inst._logger.debug("no friends without avatars left to update, "
  952.                            + "kill the timer");
  953.         inst._friendsTimer.cancel();
  954.         inst._friendsTimer = null;
  955.       }
  956.     }
  957.   };
  958.  
  959.   // Start the timer to update the account's friends avatars
  960.   if (!this._friendsTimer && friendsList.length > 0) {
  961.     this._logger.debug("found friends without avatars to update, "
  962.                        + "start the timer");
  963.     this._friendsTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  964.     this._friendsTimer.initWithCallback(friendsTimerCallback,
  965.                                         FRIENDS_AVATAR_TIMEOUT,
  966.                                         Ci.nsITimer.TYPE_REPEATING_SLACK);
  967.  
  968.     // Once we've started updating friends without avatars, we only want to
  969.     // check once per session so as not to incur a performance hit
  970.     this._updateFriendAvatarsComplete = true;
  971.   }
  972. }
  973.  
  974. youtubeService.prototype._updateFriendAvatar =
  975. function youtubeService__updateFriendAvatar(aDocument, aAccountID) {
  976.   if (aDocument && aAccountID) {
  977.     var results = Cc[PROPERTY_BAG_CONTRACTID]
  978.                   .createInstance(Ci.nsIWritablePropertyBag2);
  979.  
  980.     if (this.webDetective.detect("youtube", "page", aDocument, results)) {
  981.       var friendID = results.getPropertyAsAString("friend");
  982.  
  983.       // Only need to update the friend avatar if we're visiting the friend's page
  984.       if (aAccountID != friendID) {
  985.         var friendURN = _getIdentityUrn(aAccountID, friendID);
  986.         if (friendURN) {
  987.           var friend = this.faves_coop.get(friendURN);
  988.           var avatar = results.getPropertyAsAString("avatar");
  989.           avatar = this._fixRelativeAvatarUrl(avatar);
  990.  
  991.           // Only need to update the friend avatar if it's changed
  992.           if (friend && friend.avatar != avatar) {
  993.             friend.avatar = avatar;
  994.           }
  995.         }
  996.       }
  997.     }
  998.   }
  999. }
  1000.  
  1001. /**
  1002.  * The avatar URL we scraped may be a relative URL. If so, prepend
  1003.  * "http://www.youtube.com/" to it.
  1004.  */
  1005. youtubeService.prototype._fixRelativeAvatarUrl = 
  1006. function youtubeService__fixRelativeAvatarUrl(aUrl) {
  1007.   var url = aUrl;
  1008.  
  1009.   if (url.indexOf("http") != 0) {
  1010.     url = "http://www.youtube.com";
  1011.     // Do we need to add a leading slash?
  1012.     if (aUrl.indexOf("/") != 0) {
  1013.       url += "/";
  1014.     }
  1015.     url += aUrl;
  1016.   }
  1017.  
  1018.   return url;
  1019. }
  1020.  
  1021. youtubeService.prototype._personUpdateRequired =
  1022. function youtubeService__personUpdateRequired(aCoopPerson, aPerson) {
  1023.   return (aCoopPerson.name != aPerson.accountId);
  1024. }
  1025.  
  1026. youtubeService.prototype.addCoopPerson =
  1027. function youtubeService_addCoopPerson(aPhotoPerson, aCoopAccount)
  1028. {
  1029.   var person = aPhotoPerson;
  1030.   var identityUrn = _getIdentityUrn(aCoopAccount.accountId,
  1031.                                     person.accountId);
  1032.   var updating = this.faves_coop.Identity.exists(identityUrn);
  1033.   var identity;
  1034.   if (updating) {
  1035.     identity = this.faves_coop.get(identityUrn);
  1036.     if (this._personUpdateRequired(aCoopAccount,person)) {
  1037.       // Update data of the identity coop obj here
  1038.       identity.name = person.accountId;
  1039.       this._incrementMedia(identity, person);
  1040.     }
  1041.   } else {
  1042.     identity = new this.faves_coop.Identity(
  1043.       identityUrn,
  1044.       {
  1045.         name: person.accountId,
  1046.         serviceId: YOUTUBE_CONTRACTID,
  1047.         accountId: person.accountId,
  1048.         avatar: "",
  1049.         statusMessage: "",
  1050.         lastUpdateType: "media",
  1051.         lastUploadedMedia: person.videoCount
  1052.       }
  1053.     );
  1054.     aCoopAccount.friendsList.children.add(identity);
  1055.     this._addIdentityToActivityArray(identity);
  1056.   }
  1057. }
  1058.  
  1059. youtubeService.prototype._addIdentityToActivityArray =
  1060. function youtubeService__addIdentityToActivityArray(aIdentity) {
  1061.   this._logger.info("._addIdentityToActivityArray('"+ aIdentity.name + "')");
  1062.   
  1063.    //only want to add to array when friendsActivity timer is ready
  1064.   if (!this._updateFriendActivityComplete) {
  1065.     this._friendActivityArray.push(aIdentity);
  1066.   }
  1067. }
  1068.  
  1069. youtubeService.prototype._incrementMedia =
  1070. function youtubeService__incrementMedia(identity, person) {
  1071.   this._logger.info("._incrementMedia('"+identity.id() + "," + person.accountId +"')");
  1072.   
  1073.   if (identity.lastUploadedMedia != person.videoCount) {
  1074.     identity.lastUploadedMedia = person.videoCount;
  1075.     identity.unseenMedia = 1;
  1076.     this._addIdentityToActivityArray(identity);
  1077.   }
  1078. }
  1079.  
  1080. youtubeService.prototype._updateFriendRecentActivity =
  1081. function youtubeService__updateFriendRecentActivity(aIndividualSuccess) {
  1082.   this._logger.info("._updateFriendRecentActivity");
  1083.   var success = aIndividualSuccess;
  1084.   if (this._updateFriendActivityComplete ||
  1085.      this._friendActivityArray.length == 0)
  1086.   {
  1087.     this._onIndividualSuccess(success);
  1088.     return;
  1089.   }
  1090.   // We are successful for refresh after each friend in _friendActivityArray
  1091.   // list is updated
  1092.   success.refreshNumber += (this._friendActivityArray.length -1);
  1093.   var inst = this;
  1094.  
  1095.   var friendsTimerCallback = {
  1096.     notify: function friendNotify(aTimer) {
  1097.       inst._updateFriendActivityComplete = true;
  1098.       while(inst._friendActivityArray.length != 0)
  1099.       {
  1100.         var identity = inst._friendActivityArray.pop();
  1101.         inst._updateLastUpdateDate(identity, success);
  1102.         inst._logger.info("updating recent activity for " + identity.name);
  1103.       }
  1104.       inst._logger.debug("no friends without avatars left to update, "
  1105.                           + "kill the timer");
  1106.       inst._friendsActivityTimer.cancel();
  1107.       inst._friendsActivityTimer = null;
  1108.       inst._updateFriendActivityComplete = false;
  1109.       inst._friendActivityArray = null;
  1110.       inst._friendActivityArray = [];
  1111.       inst._logger.info("done updating recent activity");
  1112.     }
  1113.   };
  1114.  
  1115.   // Start the timer to update the account's friends avatars
  1116.   if (!this._friendsActivityTimer) {
  1117.     this._logger.debug("found friends without avatars to update, "
  1118.                        + "start the timer");
  1119.     this._friendsActivityTimer = Cc["@mozilla.org/timer;1"]
  1120.                                    .createInstance(Ci.nsITimer);
  1121.     this._friendsActivityTimer.initWithCallback(friendsTimerCallback,
  1122.                                         FRIENDS_ACTIVITY_TIMEOUT,
  1123.                                         Ci.nsITimer.TYPE_REPEATING_SLACK);
  1124.     this._updateFriendActivityComplete = false;
  1125.   }
  1126. }
  1127.  
  1128. youtubeService.prototype._updateLastUpdateDate =
  1129. function youtubeService__updateLastUpdateDate(aIdentity, aInvidualSuccessObj)
  1130. {
  1131.   this._logger.info("._updateLastUpdateDate('"+ aIdentity.name + "')");
  1132.   
  1133.   var identity = aIdentity;
  1134.   var params = {
  1135.     user: identity.name,
  1136.     page: 1,
  1137.     per_page: 1,
  1138.   };
  1139.  
  1140.   var inst = this;
  1141.   var myListener = {
  1142.     onResult: function (aXML) {
  1143.       inst._logger.info("onResult");
  1144.       var video = inst.handlePhotosResult(aXML);
  1145.       if (video.length == 1) {
  1146.         video = video[0];
  1147.         //Youtube gives us UNIX time in seconds, change to miliseconds
  1148.         var time = video.uploadDate / 1000;
  1149.         identity.lastUpdate = time;
  1150.       }
  1151.       inst._onIndividualSuccess(aInvidualSuccessObj);
  1152.     },
  1153.     onError: function (aError) {
  1154.       if (!identity.lastUpdate) {
  1155.         identity.lastUpdate = inst._numberFromString(identity.name);
  1156.       }
  1157.     }
  1158.   };
  1159.  
  1160.   this.call(myListener, "youtube.videos.list_by_user", params);
  1161. }
  1162.  
  1163. youtubeService.prototype._handleFriendsResult =
  1164. function youtubeService__handleFriendsResult(aXML, aAccount,
  1165.                                              aInvidualSuccessObj)
  1166. {
  1167.   DEBUG("._handleFriendsResult(aXML, aAccount)");
  1168.   
  1169.   var friendList = aXML.getElementsByTagName("friend");
  1170.   var inst = this;
  1171.   var peopleHash = [];
  1172.   DEBUG(" - found "+friendList.length+" friends");
  1173.   for (var i = 0; i < friendList.length; i++) {
  1174.     var friend = friendList[i];
  1175.     var friendObj = {};
  1176.     for (var j = 0; j < friend.childNodes.length; j++)
  1177.     {
  1178.       var child = friend.childNodes[j];
  1179.       var contentNode = child.childNodes[0];
  1180.       var childContent = "";
  1181.       if (contentNode) childContent = contentNode.nodeValue;
  1182.       switch (child.tagName)
  1183.       {
  1184.         case "user":
  1185.           friendObj.accountId = childContent;
  1186.           friendObj.name = childContent;
  1187.           break;
  1188.         case "video_upload_count":
  1189.           friendObj.videoCount = childContent;
  1190.           break;
  1191.       }
  1192.     }
  1193.     peopleHash[friendObj.accountId] = friendObj;
  1194.     peopleHash[friendObj.accountId].media = {
  1195.       count: 1,
  1196.       latest: friendObj.videoCount
  1197.     };
  1198.   }
  1199.   function myWorker(aShouldYield) {
  1200.     // ADD or update existing people
  1201.     for (var uid in peopleHash) {
  1202.       inst.addCoopPerson(peopleHash[uid], aAccount);
  1203.       if (aShouldYield()) {
  1204.         yield;
  1205.       }
  1206.     }
  1207.     
  1208.     // REMOVE locally people removed on the server
  1209.     var localEnum = aAccount.friendsList.children.enumerate();
  1210.     while (localEnum.hasMoreElements()) {
  1211.       var identity = localEnum.getNext();
  1212.       if (!peopleHash[identity.accountId]) {
  1213.         inst._logger.info("Friend " + identity.accountId
  1214.                                     + " has been deleted on the server");
  1215.         aAccount.friendsList.children.remove(identity);
  1216.         identity.destroy();
  1217.       }
  1218.     }
  1219.     inst._onIndividualSuccess(aInvidualSuccessObj);
  1220.     inst._updateFriendRecentActivity(aInvidualSuccessObj);
  1221.     inst._updateFriendAvatars(aAccount.id(), aInvidualSuccessObj);
  1222.   } // end myWorker()
  1223.     
  1224.   FlockScheduler.schedule(null, 0.05, 10, myWorker);
  1225. }
  1226.  
  1227. var loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
  1228.                        .getService(Components.interfaces.mozIJSSubScriptLoader);
  1229. loader.loadSubScript("chrome://browser/content/utilityOverlay.js");
  1230.  
  1231. // BEGIN flockIWebService interface
  1232. youtubeService.prototype.addAccountById =
  1233. function youtubeService_addAccountById(aAccountID, aIsTransient, aListener)
  1234. {
  1235.   DEBUG("{flockIWebService}.addAccountById('"+aAccountID+"', "+aIsTransient+", aListener)");
  1236.  
  1237.   var accountURN = "urn:flock:youtube:"+aAccountID;
  1238.   var account = this.faves_coop.get(accountURN);
  1239.   if (!account) {
  1240.     account = new this.faves_coop.Account(
  1241.       accountURN,
  1242.       {
  1243.         name: aAccountID,
  1244.         serviceId: YOUTUBE_CONTRACTID,
  1245.         service: this.ytService,
  1246.         accountId: aAccountID,
  1247.         URL: gStrings["userprofile"].replace("%accountid%", aAccountID),
  1248.         isTransient: aIsTransient,
  1249.         refreshInterval: YOUTUBE_REFRESH_INTERVAL,
  1250.         favicon: YOUTUBE_FAVICON
  1251.       }
  1252.     );
  1253.     this.account_root.children.add(account);
  1254.   }
  1255.   if (!account.friendsList) {
  1256.     var friendsListUrn = accountURN + ":friends";
  1257.     var friendsList = new this.faves_coop.FriendsList(
  1258.       friendsListUrn,
  1259.       {
  1260.         account: account
  1261.       }
  1262.     );
  1263.     account.friendsList = friendsList;
  1264.   }
  1265.   this.USER = account.id();
  1266.  
  1267.   // Instanciate account component
  1268.   var acct = this.getAccount(account.id());
  1269.   if (aListener) aListener.onSuccess(acct, "addAccount");
  1270.   return acct;
  1271. }
  1272. // END flockIWebService interface
  1273.  
  1274.  
  1275. // BEGIN flockIManageableWebService interface
  1276. youtubeService.prototype.updateAccountStatusFromDocument =
  1277. function youtubeService_updateAccountStatusFromDocument(aDocument)
  1278. {
  1279.   this._logger.info("{flockIManageableWebService}.updateAccountStatusFromDocument()");
  1280.   if (this.webDetective.detect("youtube", "loggedout", aDocument, null))
  1281.   {
  1282.     this.acUtils.markAllAccountsAsLoggedOut(YOUTUBE_CONTRACTID);
  1283.   } else if (this.webDetective.detect("youtube", "loggedin", aDocument, null)) {
  1284.     var results = Components.classes["@mozilla.org/hash-property-bag;1"]
  1285.                             .createInstance(Components.interfaces.nsIWritablePropertyBag2);
  1286.     if (this.webDetective.detect("youtube", "accountinfo", aDocument, results)) {
  1287.       var accountID = results.getPropertyAsAString("accountid");
  1288.       if (accountID && accountID.length) {
  1289.         var accountURN = this.acUtils.getAccountURNById(this.urn, accountID);
  1290.         var acct = this.faves_coop.get(accountURN);
  1291.         if (!acct.isAuthenticated) {
  1292.           var inst = this; 
  1293.           var loginListener = {
  1294.             onSuccess: function loginListener_onSuccess() {
  1295.               inst._logger.debug(".updateAccountStatusFromDocument(): "
  1296.                                 + "loginListener: onSuccess()");
  1297.               acct.isAuthenticated = true;
  1298.               inst._updateFriendAvatarsComplete = false;
  1299.             },
  1300.             onError: function loginListener_onError(aError) {
  1301.               inst._logger.debug(".updateAccountStatusFromDocument(): "
  1302.                                 + "loginListener: onError()");
  1303.               inst._logger.debug(aError ? (aError.errorString) : "No details");
  1304.             }
  1305.           };
  1306.           this.getAccount(accountURN).login(loginListener);
  1307.         }
  1308.       }
  1309.     }
  1310.   }
  1311. }
  1312. // END flockIManageableWebService interface
  1313.  
  1314.  
  1315. // BEGIN flockISocialWebService interface
  1316. // END flockISocialWebService interface
  1317.  
  1318.  
  1319. // BEGIN flockIMediaWebService interface
  1320.  
  1321. youtubeService.prototype.migrateAccount =
  1322. function youtubeService_migrateAccount(aId, aUsername) {
  1323.   this.init();
  1324.   this.addAccountById(aId, false, null);
  1325. }
  1326.  
  1327. youtubeService.prototype.decorateForMedia =
  1328. function youtubeService_decorateForMedia(aDocument)
  1329. {
  1330.   DEBUG("{flockIMediaWebService}.decorateForMedia(aDocument)");
  1331.   aDocument.QueryInterface(Components.interfaces.nsIDOMHTMLDocument);
  1332.   var results = Components.classes["@mozilla.org/hash-property-bag;1"]
  1333.                           .createInstance(Components.interfaces.nsIWritablePropertyBag2);
  1334.   if (this.webDetective.detect("youtube", "media", aDocument, results)) {
  1335.     var mediaArr = [];
  1336.  
  1337.     // media item for user videos
  1338.     var userid = results.getPropertyAsAString("userid");
  1339.     var media = {
  1340.       name: userid,
  1341.       query: 'user:' + userid + "|username:" + userid,
  1342.       label: userid + "'s Videos", // FIXME: breaks internationalization
  1343.       favicon: this.icon,
  1344.       service: this.shortName
  1345.     }
  1346.     mediaArr.push(media);
  1347.  
  1348.     // media item for user favorites
  1349.     var media = {
  1350.       name: userid,
  1351.       query: 'favorites:' + userid,
  1352.       label: userid + "'s Favorites", // FIXME: breaks internationalization
  1353.       favicon: this.icon,
  1354.       service: this.shortName
  1355.     }
  1356.     mediaArr.push(media);
  1357.  
  1358.     if (!aDocument._flock_decorations) {
  1359.       aDocument._flock_decorations = {};
  1360.     }
  1361.     aDocument._flock_decorations.mediaArr = mediaArr;
  1362.     this.obs.notifyObservers(aDocument, 'media', 'media:update');
  1363.   }
  1364. }
  1365.  
  1366. youtubeService.prototype.handlesMediaStream =
  1367. function youtubeService_handlesMediaStream() 
  1368. {
  1369.   return true;
  1370. }
  1371.  
  1372. youtubeService.prototype.checkIsStreamUrl =
  1373. function youtubeService_checkIsStreamUrl(aUrl)
  1374. {
  1375.   if (this.webDetective.detectNoDOM("youtube", "isStreamUrl", "", aUrl, null)) {
  1376.     this._logger.debug("Checking if url is youtube stream: YES: " + aUrl);
  1377.     return true;
  1378.   }
  1379.   this._logger.debug("Checking if url is youtube stream: NO: " + aUrl);
  1380.   return false;
  1381. }
  1382.  
  1383. youtubeService.prototype.getVideoIDFromUrl = 
  1384. function youTubeService_getVideoIDFromUrl(aUrl)
  1385. {
  1386.   var videoId = null;
  1387.   //http://www.youtube.com/v/lp_daXRkOH0
  1388.   //http://www.youtube.com/watch/v/YP2rgi978tw
  1389.   //http://www.youtube.com/watch?v=BjfbS_Kj-J0
  1390.   // /player2.swf?video_id=xPxDw7ajfGE&....
  1391.   var detectResults = Cc["@mozilla.org/hash-property-bag;1"]
  1392.                       .createInstance(Ci.nsIWritablePropertyBag2);
  1393.   if (this.webDetective.detectNoDOM("youtube", "videoId", "", aUrl, detectResults)) {
  1394.     videoId = detectResults.getPropertyAsAString("videoId");
  1395.   }
  1396.   return videoId;
  1397. }
  1398.  
  1399. youtubeService.prototype.getMediaQueryFromURL =
  1400. function youTubeService_getMediaQueryFromURL(aUrl, aListener)
  1401. {
  1402.   var videoId = this.getVideoIDFromUrl(aUrl);
  1403.   if (videoId) {
  1404.     var myListener = {
  1405.       onResult: function (aXML) {
  1406.         var userID = aXML.getElementsByTagName('author')[0].firstChild.nodeValue;
  1407.         var results = Components.classes["@mozilla.org/hash-property-bag;1"]
  1408.                                 .createInstance(Components.interfaces.nsIWritablePropertyBag2);
  1409.         results.setPropertyAsAString("query", "user:" + userID + "|username:" + userID);
  1410.         // Note: The "title" property is currently not used by the media bar.
  1411.         results.setPropertyAsAString("title", userID);
  1412.         aListener.onSuccess(results, "query");
  1413.       },
  1414.       onError: function (aError) {
  1415.        aListener.onError(null, aError, null);
  1416.       }
  1417.     }
  1418.     var params = {};
  1419.     params.video_id = videoId;
  1420.     this.call(myListener, "youtube.videos.get_details", params);
  1421.   } else {
  1422.     aListener.onError(null, "Unable to get user.", null);
  1423.   }
  1424. }
  1425. // END flockIMediaWebService interface
  1426.  
  1427.  
  1428. // Checks if the TEXTAREA is drag and droppable
  1429. youtubeService.prototype._isDnDableTextarea =
  1430. function youtubeService__isDnDableTextarea(aDocument, aXPath, aTextarea)
  1431. {
  1432.   if (aDocument && aXPath && aTextarea) {
  1433.     var xpath = this.webDetective.getString("youtube", aXPath, "");
  1434.     var results = aDocument.evaluate(xpath, aDocument, null,
  1435.                                      Ci.nsIDOMXPathResult.ANY_TYPE, null);
  1436.     if (results && results.iterateNext() == aTextarea) {
  1437.       return true;
  1438.     }
  1439.   }
  1440.   return false;
  1441. }
  1442.  
  1443.  
  1444. // BEGIN flockIRichContentDropHandler
  1445. youtubeService.prototype.handleDrop =
  1446. function youtubeService_handleDrop(aFlavours, aTextarea)
  1447. {
  1448.   this._logger.info(".handleDrop()");
  1449.  
  1450.   var dropCallback = function youtube_dropCallback(aFlav) {
  1451.     var data = {}, len = {};
  1452.     aFlavours.getTransferData(aFlav, data, len);
  1453.     var caretPos = aTextarea.selectionEnd;
  1454.     var currentValue = aTextarea.value;
  1455.     // Add a trailing space so that we don't mangle the url
  1456.     var nextChar = currentValue.charAt(caretPos);
  1457.     var trailingSpace = ((nextChar == "") ||
  1458.                          (nextChar == " ") || 
  1459.                          (nextChar == "\n"))
  1460.                       ? ""
  1461.                       : " ";
  1462.     // Only add a breadcrumb if the insertion point is at the end of
  1463.     // the text so that we don't duplicate breadcrumbs
  1464.     var breadcrumb = (aTextarea.value.length == aTextarea.selectionEnd)
  1465.                    ? Cc[FLOCK_RDDS_CONTRACTID]
  1466.                        .getService(Ci.flockIRichDNDService)
  1467.                        .getBreadcrumb("plain")
  1468.                    : "";
  1469.  
  1470.     aTextarea.value = currentValue.substring(0, caretPos)
  1471.                     + data.value.QueryInterface(Ci.nsISupportsString)
  1472.                           .data.replace(/: /, ":\n")
  1473.                     + trailingSpace
  1474.                     + currentValue.substring(caretPos)
  1475.                     + breadcrumb;
  1476.   };
  1477.  
  1478.   return this._handleTextareaDrop(CATEGORY_ENTRY_NAME, this.ytService.domains,
  1479.                                   aTextarea, dropCallback);
  1480. }
  1481. // END flockIRichContentDropHandler
  1482.  
  1483. youtubeService.prototype.maxStatusLength = 0;
  1484.  
  1485. // ========== END youtubeService class ==========
  1486.  
  1487.  
  1488.  
  1489. // ================================================
  1490. // ========== BEGIN youtubeAccount class ==========
  1491. // ================================================
  1492.  
  1493. function youtubeAccount()
  1494. {
  1495.   this.acUtils = Components.classes["@flock.com/account-utils;1"]
  1496.                            .getService(Components.interfaces.flockIAccountUtils);
  1497.   this.service = Components.classes[YOUTUBE_CONTRACTID]
  1498.                            .getService(Components.interfaces.flockIWebService)
  1499.                            .QueryInterface(Components.interfaces.flockISocialWebService);
  1500.   this._coop = Components.classes["@flock.com/singleton;1"]
  1501.                          .getService(Components.interfaces.flockISingleton)
  1502.                          .getSingleton("chrome://flock/content/common/load-faves-coop.js")
  1503.                          .wrappedJSObject;
  1504.   this.webDetective = Cc["@flock.com/web-detective;1"]
  1505.                       .getService(Ci.flockIWebDetective);
  1506.   this._ctk = {
  1507.     interfaces: [
  1508.       "nsISupports",
  1509.       "flockIWebServiceAccount",
  1510.       "flockIMediaWebServiceAccount",
  1511.       "flockISocialWebServiceAccount",
  1512.       "flockIYoutubeAccount"
  1513.     ]
  1514.   };
  1515.   getCompTK().addAllInterfaces(this);
  1516.  
  1517.   this._logger = Cc["@flock.com/logger;1"].createInstance(Ci.flockILogger);
  1518.   this._logger.init("youtubeAccount");
  1519. }
  1520.  
  1521.  
  1522. // BEGIN flockIWebServiceAccount interface
  1523. youtubeAccount.prototype.urn = "";
  1524. youtubeAccount.prototype.username = "";
  1525. youtubeAccount.prototype.status = "";
  1526.  
  1527. youtubeAccount.prototype.activate =
  1528. function youtubeAccount_activate(aListener)
  1529. {
  1530.   DEBUG("{flockIWebServiceAccount}.activate()");
  1531.   var acctCoopObj = this._coop.get(this.urn);
  1532.   acctCoopObj.isPollable = true;
  1533.   if (aListener) {
  1534.     aListener.onSuccess(this, "accountAuthorized");
  1535.   }
  1536. }
  1537.  
  1538. youtubeAccount.prototype.login =
  1539. function youtubeAccount_login(aListener)
  1540. {
  1541.   DEBUG("{flockIWebServiceAccount}.login()");
  1542.   this.acUtils.ensureOnlyAuthenticatedAccount(this.urn);
  1543.   // force refresh on login
  1544.   var pollerSvc = Cc["@flock.com/poller-service;1"]
  1545.                   .getService(Ci.flockIPollerService);
  1546.   pollerSvc.forceRefresh(this.urn);
  1547.   aListener.onSuccess();
  1548. }
  1549. // END flockIWebServiceAccount interface
  1550.  
  1551.  
  1552. // BEGIN flockISocialWebServiceAccount interface
  1553. youtubeAccount.prototype.hasFriendActions = true;
  1554. youtubeAccount.prototype.isStatusSupported = false;
  1555. youtubeAccount.prototype.isStatusEditable  = false;
  1556. youtubeAccount.prototype.isPostLinkSupported = false;
  1557. youtubeAccount.prototype.isMyMediaFavoritesSupported = true;
  1558.  
  1559. youtubeAccount.prototype.setStatus =
  1560. function youtubeAccount_setStatus(aStatusMessage, aListener)
  1561. {
  1562.   this._logger.info("{flockISocialWebServiceAccount}.setStatus('"+aStatusMessage+"',"+
  1563.                                                               "'"+aListener+"')");
  1564. }
  1565.  
  1566. youtubeAccount.prototype.getEditableStatus =
  1567. function youtubeAccount_getEditableStatus()
  1568. {
  1569.   this._logger.info("{flockISocialWebServiceAccount}.getEditableStatus()");
  1570.   return "";
  1571. }
  1572.  
  1573. youtubeAccount.prototype.formatStatusForDisplay =
  1574. function youtubeAccount_formatStatusForDisplay(aStatusMessage)
  1575. {
  1576.   return "";
  1577. }
  1578.  
  1579. youtubeAccount.prototype.getMeNotifications =
  1580. function youtubeAccount_getMeNotifications()
  1581. {
  1582.   this._logger.info(".getMeNotifications()");
  1583.  
  1584.   var sbs = Cc["@mozilla.org/intl/stringbundle;1"]
  1585.             .getService(Ci.nsIStringBundleService);
  1586.   var bundle = sbs.createBundle(YOUTUBE_STRING_BUNDLE);
  1587.  
  1588.   var noties = [];
  1589.   var inst = this;
  1590.   function _addNotie(aType, aCount) {
  1591.     var stringName = "flock.youtube.noties."
  1592.                    + aType + "."
  1593.                    + ((parseInt(aCount) <= 0) ? "none" : "some");
  1594.     inst._logger.info("aType " + aType + ' aCount ' + aCount + ' name ' + stringName);
  1595.     noties.push({
  1596.       class: aType,
  1597.       tooltip: bundle.GetStringFromName(stringName),
  1598.       metricsName: aType,
  1599.       count: aCount,
  1600.       URL: inst.webDetective.getString("youtube", aType + "_URL", "")
  1601.     });
  1602.   }
  1603.   var c_acct = this._coop.get(this.urn);
  1604.   _addNotie("meMessages", c_acct.accountMessages);
  1605.   return JSON.toString(noties);
  1606. }
  1607.  
  1608. youtubeAccount.prototype.markAllMeNotificationsSeen =
  1609. function youtubeAccount_markAllMeNotificationsSeen(aType) {
  1610.   this._logger.debug(".markAllMeNotificationsSeen('" + aType + "')");
  1611.   var c_acct = this._coop.get(this.urn);
  1612.   switch (aType) {
  1613.     case "meMessages":
  1614.       c_acct.accountMessages = 0;
  1615.       break;
  1616.     default:
  1617.       break;
  1618.   }
  1619. }
  1620.  
  1621. youtubeAccount.prototype.getFriendActions =
  1622. function youtubeAccount_getFriendActions(aFriendURN)
  1623. {
  1624.   this._logger.info(".getFriendActions('" + aFriendURN + "')");
  1625.  
  1626.   var actionNames = ["friendMessage",
  1627.                      "friendViewProfile",
  1628.                      "friendMediaFave",
  1629.                      "friendShareFlock"];
  1630.  
  1631.   var sbs = Cc["@mozilla.org/intl/stringbundle;1"]
  1632.             .getService(Ci.nsIStringBundleService);
  1633.   var bundle = sbs.createBundle(YOUTUBE_STRING_BUNDLE);
  1634.  
  1635.   var actions = [];
  1636.   var c_friend = this._coop.get(aFriendURN);
  1637.   if (c_friend) {
  1638.     var c_acct = this._coop.get(this.urn);
  1639.     for each (var i in actionNames) {
  1640.       actions.push({
  1641.         label: bundle.GetStringFromName("flock.youtube.actions." + i),
  1642.         class: i,
  1643.         spec: this.webDetective.getString("youtube", i, "")
  1644.                   .replace("%accountid%", c_acct.accountId)
  1645.                   .replace("%friendid%", c_friend.accountId)
  1646.       });
  1647.     }
  1648.   }
  1649.   return JSON.toString(actions);
  1650. }
  1651.  
  1652. youtubeAccount.prototype.getSharingAction =
  1653. function youtubeAccount_getSharingAction(aFriendURN, aTransferable)
  1654. {
  1655.   this._logger.info(".getSharingAction('" + aFriendURN + "')");
  1656.  
  1657.   var sharingAction = "";
  1658.   var c_friend = this._coop.get(aFriendURN);
  1659.   if (c_friend) {
  1660.     var flavors = ["text/x-flock-media",
  1661.                    "text/x-moz-url",
  1662.                    "text/unicode",
  1663.                    "text/html"];
  1664.  
  1665.     var message = Cc[FLOCK_RDDS_CONTRACTID]
  1666.                   .getService(Ci.flockIRichDNDService)
  1667.                   .getMessageFromTransferable(aTransferable,
  1668.                                               flavors.length,
  1669.                                               flavors);
  1670.     if (!message.body) {
  1671.       return sharingAction;
  1672.     } else {
  1673.       var videoId = this.service.getVideoIDFromUrl(message.body);
  1674.       if (videoId) {
  1675.         sharingAction = this.webDetective.getString("youtube",
  1676.                                                     "shareAction_sharevideo",
  1677.                                                     "");
  1678.       } else {
  1679.         sharingAction = this.webDetective.getString("youtube",
  1680.                                                     "shareAction_privatemessage",
  1681.                                                     "");
  1682.       }
  1683.     }
  1684.   }
  1685.   this._logger.info(sharingAction);
  1686.   return sharingAction;
  1687. }
  1688.  
  1689. youtubeAccount.prototype.getProfileURLForFriend =
  1690. function youtubeAccount_getProfileURLForFriend(aFriendURN)
  1691. {
  1692.   this._logger.info(".getProfileURLForFriend('" + aFriendURN + "')");
  1693.  
  1694.   var url = "";
  1695.   var c_friend = this._coop.get(aFriendURN);
  1696.   if (c_friend) {
  1697.     url = this.webDetective.getString("youtube", "friendprofile", "")
  1698.                            .replace("%accountid%", c_friend.accountId);
  1699.   }
  1700.  
  1701.   return url;
  1702. }
  1703.  
  1704. youtubeAccount.prototype.getPostLinkAction =
  1705. function youtubeAccount_getPostLinkAction(aTransferable)
  1706. {
  1707.   return "";
  1708. }
  1709. // END flockISocialWebServiceAccount interface
  1710.  
  1711. // BEGIN flockIYoutubeAccount interface
  1712. youtubeAccount.prototype.shareFlock =
  1713. function youtubeAccount_shareFlock(aFriendURN) 
  1714. {
  1715.   this._logger.info(".shareFlock('" + aFriendURN + "')");
  1716.   
  1717.   var sbs = Cc["@mozilla.org/intl/stringbundle;1"]
  1718.               .getService(Ci.nsIStringBundleService);
  1719.   var bundle = sbs.createBundle(YOUTUBE_STRING_BUNDLE);
  1720.   var body = bundle.GetStringFromName("flock.youtube.friendShareFlock.message");
  1721.   var subj = bundle.GetStringFromName("flock.youtube.friendShareFlock.subject");
  1722.   this._composeMessage(aFriendURN, subj, body, false);
  1723. }
  1724.  
  1725. youtubeAccount.prototype.youtubePrivateMessage =
  1726. function YoutubeAccount_youtubePrivateMessage(aFriendURN, aTransferable)
  1727. {
  1728.   this._logger.info(".youtubePrivateMessage('" + aFriendURN + "')");
  1729.  
  1730.   var flavors = ["text/x-flock-media",
  1731.                  "text/x-moz-url",
  1732.                  "text/unicode",
  1733.                  "text/html"];
  1734.  
  1735.   var message = Cc[FLOCK_RDDS_CONTRACTID]
  1736.                 .getService(Ci.flockIRichDNDService)
  1737.                 .getMessageFromTransferable(aTransferable,
  1738.                                             flavors.length,
  1739.                                             flavors);
  1740.   if (message.body) {
  1741.     this._composeMessage(aFriendURN, message.subject, message.body, true);
  1742.   }
  1743. }
  1744.  
  1745. youtubeAccount.prototype.youtubeShareVideo =
  1746. function youtubeAccount_youtubeShareVideo(aFriendURN, aTransferable)
  1747. {
  1748.   this._logger.info(".youtubeShareVideo('" + aFriendURN + "')");
  1749.  
  1750.   var flavors = ["text/x-flock-media",
  1751.                  "text/x-moz-url",
  1752.                  "text/unicode",
  1753.                  "text/html"];
  1754.  
  1755.   var message = Cc[FLOCK_RDDS_CONTRACTID]
  1756.                 .getService(Ci.flockIRichDNDService)
  1757.                 .getMessageFromTransferable(aTransferable,
  1758.                                             flavors.length,
  1759.                                             flavors);
  1760.   if (message.body) {
  1761.     var inst = this;
  1762.     var share_url = inst.webDetective.getString("youtube",
  1763.                                                 "shareIframeURL", "");
  1764.     var obs = Cc["@mozilla.org/observer-service;1"]
  1765.                 .getService(Ci.nsIObserverService);
  1766.     var videoId = this.service.getVideoIDFromUrl(message.body);
  1767.     var url = this.webDetective.getString("youtube", "youtubeShare_URL", "")
  1768.                   .replace("%videoid%", videoId);
  1769.     var wm = Cc["@mozilla.org/appshell/window-mediator;1"]
  1770.                .getService(Ci.nsIWindowMediator);
  1771.     var win = wm.getMostRecentWindow("navigator:browser");
  1772.     var dimen = inst.webDetective.getString("youtube",
  1773.                                             "shareAction_sharevideo_features",
  1774.                                             "");
  1775.     win.open(url, "Share", dimen);
  1776.     win = wm.getMostRecentWindow("navigator:browser");
  1777.     flockDocumentReadyObserver = {
  1778.       observe: function openShareVideo_FormFill (aSubject, aTopic, aData) {
  1779.         if (aData == share_url) {
  1780.           obs.removeObserver(this, "FlockDocumentReady");
  1781.           var contentWindow = win.gBrowser.docShell
  1782.                                  .QueryInterface(Ci.nsIInterfaceRequestor)
  1783.                                  .getInterface(Ci.nsIDOMWindow);
  1784.  
  1785.           function insertContent(aDoc, aXpathquery, aAction, aMessage) {
  1786.             var formItems = aDoc.evaluate(aXpathquery, aDoc, null,
  1787.                                           Ci.nsIDOMXPathResult.ANY_TYPE, null);
  1788.             if (formItems) {
  1789.               var formItem = formItems.iterateNext();
  1790.               if (!formItem) {
  1791.                 return;
  1792.               }
  1793.               switch (aAction) {
  1794.                 case "click":
  1795.                   formItem.click();
  1796.                   break;
  1797.  
  1798.                 case "username":
  1799.                   if (formItem.hasAttribute("value")) {
  1800.                     formItem.setAttribute("value", aMessage);
  1801.                   }
  1802.                   break;
  1803.  
  1804.                 case "message":
  1805.                   var textNode = doc.createTextNode(aMessage);
  1806.                   formItem.appendChild(textNode);
  1807.                   break;
  1808.               
  1809.                 default:
  1810.                   inst._logger.debug("no action associated with " + aAction);
  1811.               }
  1812.             }
  1813.           }
  1814.           var doc = contentWindow.document;
  1815.           // Insert Flock bread crumb
  1816.           var breadcrumb = Cc[FLOCK_RDDS_CONTRACTID]
  1817.                              .getService(Ci.flockIRichDNDService)
  1818.                              .getBreadcrumb("plain");
  1819.           if (breadcrumb) {
  1820.             var webDicName = "youtubeshareVideo_message";
  1821.             var xpathquery = inst.webDetective.getString("youtube",
  1822.                                                          webDicName, "");
  1823.             insertContent(doc, xpathquery, "message", breadcrumb);
  1824.           }
  1825.  
  1826.           // Insert friend trying to save to into iframe
  1827.           webDicName = "youtubeshareVideo_IframeXPath";
  1828.           xpathquery = inst.webDetective.getString("youtube",
  1829.                                                    webDicName, "");
  1830.           var formItems = doc.evaluate(xpathquery, doc, null,
  1831.                                        Ci.nsIDOMXPathResult.ANY_TYPE, null);
  1832.           if (formItems) {
  1833.             var iframe = formItems.iterateNext();
  1834.             if (iframe) {
  1835.               var c_friend = inst._coop.get(aFriendURN);
  1836.               webDicName = "youtubeshareVideo_friendCheckXPath";
  1837.               xpathquery = inst.webDetective.getString("youtube", webDicName, "")
  1838.                                             .replace("%friendname%", c_friend.name);
  1839.               insertContent(iframe.contentDocument, xpathquery, "click");
  1840.             }
  1841.           }
  1842.         }
  1843.       }
  1844.     };
  1845.     obs.addObserver(flockDocumentReadyObserver, 'FlockDocumentReady', false);
  1846.   }
  1847. }
  1848.  
  1849. youtubeAccount.prototype._composeMessage =
  1850. function youtubeAccount__composeMessage(aFriendURN, aSubject, aBody, addBreadCrumb)
  1851. {
  1852.   var body = aBody;
  1853.   var subject = aSubject;
  1854.   var c_friend = this._coop.get(aFriendURN);
  1855.   var url = this.webDetective.getString("youtube", "youtubeMessage_URL", "")
  1856.                 .replace("%friendid%", c_friend.accountId);
  1857.   var wm = Cc["@mozilla.org/appshell/window-mediator;1"]
  1858.              .getService(Ci.nsIWindowMediator);
  1859.   var win = wm.getMostRecentWindow("navigator:browser");
  1860.   if (win) {
  1861.    var browser = win.getBrowser();
  1862.    var newTab = browser.loadOneTab(url, null, null, null, false, false);
  1863.    var obs = Cc["@mozilla.org/observer-service;1"]
  1864.              .getService(Ci.nsIObserverService);
  1865.    var inst = this;
  1866.    var observer = {
  1867.      observe: function openSendMessageTabForFill_observer(aContent,
  1868.                                                           aTopic,
  1869.                                                           aContextUrl)
  1870.      {
  1871.        var contentWindow = newTab.linkedBrowser.docShell
  1872.                                  .QueryInterface(Ci.nsIInterfaceRequestor)
  1873.                                  .getInterface(Ci.nsIDOMWindow);
  1874.        function insertContent(aWebDicString, aMessage) {
  1875.          var xpathquery = inst.webDetective.getString("youtube", aWebDicString, "");
  1876.          var doc = contentWindow.document;
  1877.          var formItems = doc.evaluate(xpathquery, doc, null,
  1878.                                       Ci.nsIDOMXPathResult.ANY_TYPE, null);
  1879.          if (formItems) {
  1880.            var formItem = formItems.iterateNext();
  1881.            if (formItem.hasAttribute("value")) {
  1882.              formItem.setAttribute("value", aMessage);
  1883.            } else {
  1884.              var textNode = doc.createTextNode(aMessage);
  1885.              formItem.appendChild(textNode);
  1886.              inst._logger.info("aMessage " + aMessage);
  1887.            }
  1888.          }
  1889.        }
  1890.        if (contentWindow == aContent) {
  1891.          obs.removeObserver(this, "EndDocumentLoad");
  1892.          insertContent("youtubemessage_subjectXPath", subject);
  1893.          if (addBreadCrumb) {
  1894.            // Add breadcrumb to message body
  1895.             var breadcrumb = Cc[FLOCK_RDDS_CONTRACTID]
  1896.                                .getService(Ci.flockIRichDNDService)
  1897.                                .getBreadcrumb("plain");
  1898.            if (breadcrumb) {
  1899.              body += breadcrumb;
  1900.            }
  1901.         }
  1902.          insertContent("youtubemessage_bodyXPath", body);
  1903.        }
  1904.      }
  1905.    };
  1906.    obs.addObserver(observer, "EndDocumentLoad", false);
  1907.   }
  1908. }
  1909. // END flockIYoutubeAccount interface
  1910.  
  1911. // ========== END youtubeAccount class ==========
  1912.  
  1913.  
  1914.  
  1915. // ==============================================
  1916. // ========== BEGIN XPCOM registration ==========
  1917. // ==============================================
  1918.  
  1919. function createModule(aParams) {
  1920.   return {
  1921.     registerSelf: function (aCompMgr, aFileSpec, aLocation, aType) {
  1922.       aCompMgr.QueryInterface(Ci.nsIComponentRegistrar);
  1923.       aCompMgr.registerFactoryLocation( aParams.CID, aParams.componentName,
  1924.                                         aParams.contractID, aFileSpec,
  1925.                                         aLocation, aType );
  1926.       var catMgr = Cc["@mozilla.org/categorymanager;1"]
  1927.         .getService(Ci.nsICategoryManager);
  1928.       if (!aParams.categories) { aParams.categories = []; }
  1929.       for (var i = 0; i < aParams.categories.length; i++) {
  1930.         var cat = aParams.categories[i];
  1931.         catMgr.addCategoryEntry( cat.category, cat.entry,
  1932.                                  cat.value, true, true );
  1933.       }
  1934.     },
  1935.     getClassObject: function (aCompMgr, aCID, aIID) {
  1936.       if (!aCID.equals(aParams.CID)) { throw Cr.NS_ERROR_NO_INTERFACE; }
  1937.       if (!aIID.equals(Ci.nsIFactory)) { throw Cr.NS_ERROR_NOT_IMPLEMENTED; }
  1938.       return { // Factory
  1939.         createInstance: function (aOuter, aIID) {
  1940.           if (aOuter != null) { throw Cr.NS_ERROR_NO_AGGREGATION; }
  1941.           var comp = new aParams.componentClass();
  1942.           if (aParams.implementationFunc) { aParams.implementationFunc(comp); }
  1943.           return comp.QueryInterface(aIID);
  1944.         }
  1945.       };
  1946.     },
  1947.     canUnload: function (aCompMgr) { return true; }
  1948.   };
  1949. }
  1950.  
  1951. // NS Module entrypoint
  1952. function NSGetModule(aCompMgr, aFileSpec) {
  1953.   return createModule({
  1954.     componentClass: youtubeService,
  1955.     CID: YOUTUBE_CID,
  1956.     contractID: YOUTUBE_CONTRACTID,
  1957.     componentName: CATEGORY_COMPONENT_NAME,
  1958.     implementationFunc: function (aComp) { getCompTK().addAllInterfaces(aComp); },
  1959.     categories: [
  1960.       { category: "wsm-startup", entry: CATEGORY_COMPONENT_NAME, value: YOUTUBE_CONTRACTID },
  1961.       { category: "flockWebService", entry: CATEGORY_ENTRY_NAME, value: YOUTUBE_CONTRACTID },
  1962.       { category: "flockMediaProvider", entry: CATEGORY_ENTRY_NAME, value: YOUTUBE_CONTRACTID },
  1963.       { category: "flockRichContentHandler", entry: CATEGORY_ENTRY_NAME, value: YOUTUBE_CONTRACTID }
  1964.     ]
  1965.   });
  1966. }
  1967.  
  1968. // ========== END XPCOM registration ==========
  1969.  
  1970.  
  1971.  
  1972. // HELPER STUFF
  1973.  
  1974. function loadSubScript(spec)
  1975. {
  1976.   var loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
  1977.                          .getService(Components.interfaces.mozIJSSubScriptLoader);
  1978.   var context = {};
  1979.   loader.loadSubScript(spec, context);
  1980.   return context;
  1981. }
  1982.  
  1983. function loadLibraryFromSpec(aSpec)
  1984. {
  1985.   var loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
  1986.                          .getService(Components.interfaces.mozIJSSubScriptLoader);
  1987.   loader.loadSubScript(aSpec);
  1988. }
  1989.